package com.sasken.pdb.controllers
{
	import com.sasken.pdb.controllers.events.GridItemEditEvent;
	import com.sasken.pdb.models.data.EmployeeWorkLoad;
	import com.sasken.pdb.models.data.EquipmentWorkLoad;
	import com.sasken.pdb.models.data.IWorkload;
	import com.sasken.pdb.models.data.ScheduleItem;
	import com.sasken.pdb.models.settings.CoordinateUtils;
	import com.sasken.pdb.models.settings.WeekDateUtil;
	import com.sasken.pdb.views.components.ScheduleBar;
	import com.sasken.pdb.views.components.ScheduleView;
	import com.sasken.pdb.views.renderers.LabelHeaderRenderer;
	import com.sasken.pdb.views.renderers.ScheduleItemRenderer;
	import com.yahoo.astra.utils.DateUtil;
	
	import flash.events.MouseEvent;
	import flash.events.TimerEvent;
	import flash.geom.Point;
	import flash.utils.Timer;
	
	import mx.collections.*;
	import mx.containers.Canvas;
	import mx.containers.VBox;
	import mx.controls.AdvancedDataGrid;
	import mx.controls.advancedDataGridClasses.*;
	import mx.core.ClassFactory;
	import mx.core.ScrollPolicy;
	import mx.events.*;	
	
	/**
	 *  This is a controller for the schedule view component used in PDB. 
	 *  It only supports the behavior specified for PDB, it cannot be used as a generic component.
	 */ 
	public class ScheduleViewController extends BaseController implements IScheduleViewStatus
	{
		private var _adg:AdvancedDataGrid;
		private var _mask:Canvas;
		private var _startDate:Date;
		private var _endDate:Date;
		
		private var scheduleModel:ArrayCollection;
		
		private var expanded:Boolean = false;
		
		/**
		 * 	How much time is measured in the current rendering mode
		 */ 
		private var _maxTimeUnit:Number;
		
		/**
		 *  The smallest amount of time measurable in current rendering mode
		 */ 
		private var _minTimeUnit:Number;
		
		// mystery
		private var icol:Number= 0;
		
		private var _editMode:Boolean;
		
		private var _editable:Boolean;
		
		/**
		 *  Start and end date bounds for Q1, Q2, Q3 and Q4 for any year.
		 */
		private var quarterDateBounds:Array;
		
		public static var RENDERER_MODE_INVALID:int = -1;
		public static var RENDERER_MODE_YEAR:int = 0;
		public static var RENDERER_MODE_QUARTER:int = 1;
		public static var RENDERER_MODE_MONTH:int = 2;
		
		private static var NORM_YEAR_COLUMN_WIDTH:int = 100;
		private static var LEAP_YEAR_COLUMN_WIDTH:int = LEAP_YEAR_RATIO * NORM_YEAR_COLUMN_WIDTH;
		
		private static var QUARTER_COLUMN_WIDTH:int = 550;
		private static var LEAP_YEAR_QUARTER_COLUMN_WIDTH:int = QUARTER_COLUMN_WIDTH;
		
		public static var QUARTER_WEEK_WIDTH:int = 65;
			
		private static var LEAP_YEAR_RATIO:Number = 366/365; 
		
		private var HSCROLLBAR_HEIGHT:int = 16;
		
		private var _rendererMode:int;
		private var _currentMonth:int =0;
		private var _currentYear:int = 0;
		
		private var _view:ScheduleView;
		
		private var scheduleTimeScale:Number;
		
		private var currentEditItem:ScheduleBar;
		
		/** paint rect stuff
		 *  messy, but big big hurry...
		 *
		 */
		private var paintRect:VBox;
		private var mouseDownCoords:Point;
		private var dragMode:Boolean; 
		private var mouseDown:Boolean;
		private var dragTimer:Timer;
		private var rectStartPos:Point;
		private var paintRectStartColumnIndex:int = 0;
		private var paintRectEndColumnIndex:int = 0;
		
		private var paintRectStartRowIndex:int = 0;
		private var paintRectEndRowIndex:int = 0;
		
		private var paintRectOffset:Point;
		private var paintRectPos:Point;
		private var paintRectSize:Point;
		private var adgScrollPosition:Point;
		
		private var _useCondensedSchedules:Boolean;
		
		public function ScheduleViewController(view:ScheduleView)
		{
			super(view);
			
			var yearInMilliseconds:Number = 1000 * 60 * 60 * 24 * 365;
			
			_editMode = false;
			_editable = true;
			
			_startDate = new Date();
			_startDate = new Date(_startDate.getTime() - yearInMilliseconds * 2);
			
			
			_endDate = new Date();
			_endDate = new Date(_endDate.getTime() + yearInMilliseconds * 2);
			
			scheduleModel = new ArrayCollection();
			
			//trace("start and end:", _startDate, _endDate);
			
			_view = view;
			
			scheduleTimeScale = 0;
			
			_view.addEventListener(FlexEvent.CREATION_COMPLETE, creationCompleteListener);
			
			mouseDownCoords = new Point();
			mouseDown = false;
			dragTimer = new Timer(250,1);
			dragTimer.addEventListener(TimerEvent.TIMER, dragTimerListener);
			dragMode = false;
			rectStartPos = new Point();	
			paintRectOffset = new Point();	
			paintRectSize = new Point();
			paintRectPos = new Point();
			adgScrollPosition = new Point();
		
			_useCondensedSchedules = false;
		
		}
		
		public function get useCondensedSchedules():Boolean
		{
			return _useCondensedSchedules;
		}
		
		public function set useCondensedSchedules(value:Boolean):void
		{
			_useCondensedSchedules = value;
		}
		
		public function getX1PosForScheduleItem(item:ScheduleItem):int
		{	
			return getCoordinateByDate(item.startDate);	
		}
		
		public function getX2PosForScheduleItem(item:ScheduleItem):int
		{	
			return getCoordinateByDate(item.endDate);	
		}
		
		
		/**
		 *  Returns a timeline coordinate by date.
		 */ 
		private function getCoordinateByDate(date:Date):Number
		{
			var schedItemStartYear:Number = date.getFullYear();
			var x:Number = 0;
			var columnWidth:Number;
			
			var absTime:Number;
			var yearStartDate:Date;
			
			var i:int;
			
			switch(_rendererMode)
			{
				case RENDERER_MODE_YEAR:
					var yearDiff:int = schedItemStartYear - _startDate.getFullYear();
					var loopYear:int = _startDate.getFullYear();
					
					if (schedItemStartYear < _startDate.getFullYear())
					{
						return 0;
					}
					
					for(i = 0; i < yearDiff; ++i)
					{
						if (DateUtil.isLeapYear(loopYear))
						{
							x += LEAP_YEAR_COLUMN_WIDTH;
						}
						else
						{
							x += NORM_YEAR_COLUMN_WIDTH;
						}
						loopYear++;
					}
					//trace("initial x: ", schedItemStartYear);
					
					columnWidth = NORM_YEAR_COLUMN_WIDTH;
					if (DateUtil.isLeapYear(schedItemStartYear))
					{
						columnWidth = LEAP_YEAR_COLUMN_WIDTH;
					}
					
					// TODO: this is not 100% accurate ISO week date conversion or anything, but 
					// it yields pretty accurate results with regard to item rendering.
					absTime = date.getTime();
					yearStartDate = new Date(date.getFullYear(),0);
					absTime -= yearStartDate.getTime();
					absTime =  (absTime * scheduleTimeScale);
					
					//trace("abs time ", absTime);
					x +=  columnWidth * (absTime) / getDaysPerYear(schedItemStartYear);
					//trace("x is ", x);
					break;
					
				case RENDERER_MODE_QUARTER:
					
					if (schedItemStartYear < _currentYear)
					{
						return 0;
					}
					else if (schedItemStartYear > _currentYear)
					{
						return  AdvancedDataGridColumn(_adg.groupedColumns[0]).width + 
							 	AdvancedDataGridColumn(_adg.groupedColumns[1]).width + 
							 	AdvancedDataGridColumn(_adg.groupedColumns[2]).width +
								AdvancedDataGridColumn(_adg.groupedColumns[3]).width;
					}
					
					columnWidth = QUARTER_COLUMN_WIDTH * 4; // four columns = one year
					
					if (DateUtil.isLeapYear(schedItemStartYear))
					{
						columnWidth = LEAP_YEAR_QUARTER_COLUMN_WIDTH * 4;
					}
					
					var month:int = date.getMonth();
					var startMonth:int = 0;
					var daysPerQuarter:int = 1;
					var quarter:int = 0;
					
					if (month >= 0 && month < 3)
					{
						// Q1
						columnWidth = AdvancedDataGridColumn(_adg.groupedColumns[0]).width;
						daysPerQuarter = DateUtil.DAYS_IN_JANUARY + DateUtil.DAYS_IN_FEBRUARY + DateUtil.DAYS_IN_MARCH;
						if (DateUtil.isLeapYear(schedItemStartYear))
						{
							daysPerQuarter++;
						}
					}
					else if (month >= 3 && month < 6)
					{
						// Q2
						x = AdvancedDataGridColumn(_adg.groupedColumns[0]).width + 2;
						columnWidth = AdvancedDataGridColumn(_adg.groupedColumns[1]).width;
						startMonth = 3;
						daysPerQuarter = 30 + 31 + 30;
						quarter = 1;
					}
					else if (month >= 6 && month < 9)
					{
						// Q3
						x = AdvancedDataGridColumn(_adg.groupedColumns[0]).width + 
							 AdvancedDataGridColumn(_adg.groupedColumns[1]).width + 2;
						columnWidth = AdvancedDataGridColumn(_adg.groupedColumns[2]).width;
						startMonth = 6;
						daysPerQuarter = 31 + 31 + 30;
						quarter = 2;
					}
					else
					{
						// Q4
						x  = AdvancedDataGridColumn(_adg.groupedColumns[0]).width + 
							 AdvancedDataGridColumn(_adg.groupedColumns[1]).width + 
							 AdvancedDataGridColumn(_adg.groupedColumns[2]).width;
						columnWidth = AdvancedDataGridColumn(_adg.groupedColumns[3]).width + 2;
						startMonth = 9;
						daysPerQuarter = 31 + 30 + 31;
						quarter = 3;
					}
					
					
					// TODO: this is not 100% accurate ISO week date conversion or anything, but 
					// it yields pretty accurate results with regard to item rendering.
					
					/*absTime = date.getTime();
					yearStartDate = new Date(date.getFullYear(), startMonth);
					absTime -= yearStartDate.getTime();
					absTime =  (absTime * scheduleTimeScale);
					
					//add one day: this is because by default, first day is 0, but 
					// it's needed for calculations
					absTime++; 
					*/
					//trace("abs time ", absTime, "date: ", date);
					//x += columnWidth * (absTime) / daysPerQuarter;
					
					// here is a slower but much more accurate way of calculating week positions.
					// the other one above is commented out because it won't work correctly in most cases.
					var schedItemWeek:int = WeekDateUtil.calculateWeekNumber(date);
					
					for(i = 0; i < _adg.groupedColumns[quarter].children.length; ++i)
					{
						var column:AdvancedDataGridColumn = _adg.groupedColumns[quarter].children[i];
						
						var colWeek:int = Number(column.headerText);
						
						if (colWeek == schedItemWeek)
						{
							break;
						}
						else
						{
							x += column.width;
						}
						
					}
				
			}
			return x;
		}
		
		public function set rendererMode(value:int):void
		{
			_rendererMode = value;
			switch(_rendererMode)
			{
				case RENDERER_MODE_YEAR:
					_editMode = false;
					_maxTimeUnit = (_endDate.getTime() - _startDate.getTime()); //(1000 * 3600 * 24 * DateUtil.DAYS_IN_YEAR);
					_minTimeUnit = 1000 * 3600 * 24 * (30 *3);
					
					if (currentEditItem)
					{
						currentEditItem.editMode = false;
						currentEditItem = null;
					}
					// converts milliseconds to days
					scheduleTimeScale = 1 / (1000 * 3600 * 24);
					break;
					
				case RENDERER_MODE_QUARTER:
					_editMode = true;
					
					var daysInYear:Number = DateUtil.DAYS_IN_YEAR;
					
					_maxTimeUnit = 1000 * 3600 * 24 * daysInYear;
					_minTimeUnit = 1000 * 3600 * 24 * 7;				
					if (currentEditItem)
					{
						currentEditItem.editMode = false;
						currentEditItem = null;
					}
					//trace(_adg.selectionMode);
					_adg.selectionMode = AdvancedDataGridBase.MULTIPLE_CELLS;
					scheduleTimeScale = 1 / (1000 * 3600 * 24);
					break;
			}
			if (paintRect)
			{
				paintRect.visible = false;
			}
		}
		
		public function get editable():Boolean
		{
			return _editable;
		}
		
		public function set editable(value:Boolean):void
		{
			_editable = value;
		}
		
		public function get editMode():Boolean
		{
			return _editMode;
		}
		
		public function get rendererMode():int
		{
			return _rendererMode;
		}
		
		public function get currentMonth():int
		{
			return _currentMonth;
		}
		
		public function get currentYear():int
		{
			return _currentYear;
		}
		
		private function creationCompleteListener(e:FlexEvent):void
		{
			_adg = _view.scheduleGrid;
			_adg.addEventListener(AdvancedDataGridEvent.HEADER_RELEASE, headerReleaseHandler);
			_adg.horizontalScrollPolicy = ScrollPolicy.ON;
			_adg.doubleClickEnabled = true;
			
			_adg.addEventListener(GridItemEditEvent.GRID_EDIT_EVENT, gridEditListener);
   			_adg.addEventListener(GridItemEditEvent.GRID_ROW_UPDATE, gridRowUpdateListener);
			
			_adg.resizableColumns = false;
			_adg.draggableColumns = false;
			
			rendererMode = RENDERER_MODE_YEAR;
			
			_adg.addEventListener(MouseEvent.MOUSE_DOWN, gridMouseDownListener);
			_adg.addEventListener(MouseEvent.MOUSE_UP, gridMouseUpListener);
			_adg.addEventListener(MouseEvent.MOUSE_MOVE, gridMouseMoveListener);
			_adg.addEventListener(ScrollEvent.SCROLL, gridScrollListener);
				
			paintRect = _view.paintRect; 	
			_view.btnPaintRectOk.addEventListener(MouseEvent.CLICK, setScheduleItemsListener);			
			
			//dataProvider = createScheduleDataProvider();
			
			updateFromModel();
			
			drawTimeLine();
			
			
		}
		
		public function set dataProvider(model:ArrayCollection):void
		{
			scheduleModel = model;
		
			model.addEventListener(CollectionEvent.COLLECTION_CHANGE, collectionChangeListener);
			updateFromModel();
		}
		
		private function collectionChangeListener(e:CollectionEvent):void
		{
			//trace("SCHED COLLECTION CHANGED!", scheduleModel.length);
			updateFromModel();
			refreshTimeline();
		}
		
		private function updateFromModel():void
		{
			if (_adg)
			{
				// copy is not done here, it's someone else's responsibility
				_adg.dataProvider = scheduleModel;
			}
		}
		private function setScheduleItemsListener(e:MouseEvent):void
		{
			var workload:int = _view.nsPaintRectInput.value;

			var tmpSwap:int = paintRectStartColumnIndex;
			if (paintRectStartColumnIndex > paintRectEndColumnIndex)
			{
				paintRectStartColumnIndex = paintRectEndColumnIndex;
				paintRectEndColumnIndex = tmpSwap;	
			}
			if (paintRectStartRowIndex > paintRectEndRowIndex)
			{
				tmpSwap = paintRectStartRowIndex;
				paintRectStartRowIndex = paintRectEndRowIndex;
				paintRectEndRowIndex = tmpSwap;
			}
			if (paintRectEndRowIndex > (_adg.dataProvider as IList).length)
			{
				paintRectEndRowIndex = (_adg.dataProvider as IList).length;
			}
			
			paintRect.visible = false;
			paintRect.mouseEnabled = false;
			paintRect.mouseChildren = false;
			
			if (_rendererMode == RENDERER_MODE_QUARTER)
			{
				for(var row:int = paintRectStartRowIndex; row < paintRectEndRowIndex; ++row)
				{

					var startCol:AdvancedDataGridColumn = _adg.columns[paintRectStartColumnIndex];
					var endCol:AdvancedDataGridColumn = _adg.columns[paintRectEndColumnIndex - 1];
					
					var startWeekNumber:int = int(startCol.headerText);
					var endWeekNumber:int = int(endCol.headerText);
					
					var startQuarter:int = -1;
					var endQuarter:int = -1;
					var i:int;
					
					var headerText:String;
					var headerYear:int;
					
					for(i = 0; i < _adg.groupedColumns.length; ++i)
					{
						var colGroup:AdvancedDataGridColumnGroup = _adg.groupedColumns[i];
						
						if (!colGroup || !colGroup.headerText) continue;
						
						
						if (Number(colGroup.headerText.substring(3)) != _currentYear)
						{
							continue;
						}
						var thisQuarter:int = Number(colGroup.headerText.substring(1,2)) - 1;
						//trace("thisQuarter", thisQuarter);
						
						var quarterIndex:int = colGroup.children.indexOf(startCol);
						if (startQuarter == -1 && quarterIndex != -1)
						{
							headerText = (colGroup.children[quarterIndex] as AdvancedDataGridColumn).headerText;
							headerYear = Number(headerText.substring(3));
							
							startQuarter = thisQuarter;
						}
						
						quarterIndex = colGroup.children.indexOf(endCol);
						if (endQuarter == -1 && quarterIndex != -1)
						{
							headerText = (colGroup.children[quarterIndex] as AdvancedDataGridColumn).headerText;
							headerYear = Number(headerText.substring(3));
							//trace("headertext: ", headerText.substring(3)); 
							endQuarter = thisQuarter; //Number(headerText.substring(1,1));
						}
						
						if (startQuarter != -1 && endQuarter != -1) 
						{
							break;
						}
						
					}
					
					var startDate:Date = getDateForQuarterWeekYear(startQuarter, startWeekNumber, _currentYear);
					var endDate:Date = getDateForQuarterWeekYear(endQuarter, endWeekNumber, _currentYear, false);
					
					//trace(startWeekNumber, endWeekNumber);
					//trace(startDate, endDate);
					
					var scheduleRow:Array = ((scheduleModel.getItemAt(row)) as IWorkload).getWorkload();
					
					var addItems:Array = []; // items to be added to the schedule row
					
					var newSchedItem:ScheduleItem;


					for(i = 0; i < scheduleRow.length; ++i)
					{
						var testSchedItem:ScheduleItem = scheduleRow[i];
						
						
						// exceeds from right side; 
						//  - chop up the item into two pieces: left side with old load and right side with new one
						if (startDate.getTime() >= testSchedItem.startDate.getTime() &&
							endDate.getTime() >= testSchedItem.endDate.getTime() && 
							startDate.getTime() <= testSchedItem.endDate.getTime() )
						{
							newSchedItem = getNewSchedItem(testSchedItem);
							newSchedItem.load = workload;
							newSchedItem.startDate = startDate;
							newSchedItem.endDate = new Date(testSchedItem.endDate.getTime());
							
							addItems.push(newSchedItem);
							
							//trace("EXCEEDED FROM RIGHT SIDE");
							//trace(startDate, testSchedItem.endDate);	
							testSchedItem.endDate = new Date(startDate.getTime());
								
						}
						// completely exceeds the existing item: 
						//  - just replace the load of the old item
						else if (startDate.getTime() <= testSchedItem.startDate.getTime() &&
								 endDate.getTime() >= testSchedItem.endDate.getTime())
						{
							testSchedItem.load = workload;
							
						}
						// exceeds from left side;
						//  - chop up the item into two pieces: left side with new load and right side with old one
						else if (startDate.getTime() <= testSchedItem.startDate.getTime() &&
								 endDate.getTime() >= testSchedItem.startDate.getTime() && 
								 endDate.getTime() <= testSchedItem.endDate.getTime()  )
						{
							newSchedItem = getNewSchedItem(testSchedItem);
							newSchedItem.load = workload;
							newSchedItem.startDate = new Date(testSchedItem.startDate.getTime());
							newSchedItem.endDate = endDate;
							
							addItems.push(newSchedItem);
								
							testSchedItem.startDate = new Date(endDate.getTime());
						}
						// add items in middle of an existing one:
						// - two additional items must be created, one for new area and one for separated area.
						else if (startDate.getTime() > testSchedItem.startDate.getTime() &&
								 endDate.getTime() < testSchedItem.endDate.getTime())
						{
							newSchedItem = getNewSchedItem(testSchedItem);
							newSchedItem.load = testSchedItem.load;
							newSchedItem.startDate = new Date(testSchedItem.startDate.getTime());
							newSchedItem.endDate = new Date(startDate.getTime());
							
							addItems.push(newSchedItem);
							
							newSchedItem = getNewSchedItem(testSchedItem);
							newSchedItem.load = workload;
							newSchedItem.startDate = new Date(startDate.getTime());
							newSchedItem.endDate = 	new Date(endDate.getTime());
							
							addItems.push(newSchedItem);
								
							testSchedItem.startDate = new Date(endDate.getTime());
						}
					}
					//}
					// add new items to array
					for(i=0; i < addItems.length; ++i)
					{
						scheduleRow.push(addItems[i]);	
					}

					
					
				}
			}
			scheduleModel.refresh();	
		}
		
		/**
		 * return a new sched item of correct type: 
		 *  either employee or equipment.
		 */ 
		private function getNewSchedItem(existingSchedItem:ScheduleItem):ScheduleItem
		{
			var sched:ScheduleItem;
			
			if (existingSchedItem is EmployeeWorkLoad)
			{
				sched = new EmployeeWorkLoad();
			}
			else if (existingSchedItem is EquipmentWorkLoad)
			{
				sched = new EquipmentWorkLoad();
			}
			else
			{
				trace("ScheduleViewController.getNewSchedItem(): Error: No proper type was found for existing schedule item ", existingSchedItem);
				sched = new ScheduleItem();
			}
			
			return sched;
		}
		
		private function getDateForQuarterWeekYear(quarter:int, week:int, year:int, isStartDate:Boolean=true):Date
		{
			var resultDate:Date;
			
			var dates:Array = 
			[
				[new Date(year, 0,1), new Date(year, 1,1), new Date(year, 2,1) ],
				[new Date(year, 3,1), new Date(year, 4,1), new Date(year, 5,1) ],
				[new Date(year, 6,1), new Date(year, 7,1), new Date(year, 8,1) ],
				[new Date(year, 9, 1),new Date(year, 10,1),new Date(year, 11,1)],
			];	
			
			var dateArray:Array = dates[quarter];
			
			for(var i:int = 0; i < dateArray.length; ++i)
			{
				var testMonthDate:Date = (dateArray[i] as Date);	
				
				var monthStartTime:Number = testMonthDate.getTime();
				
				var testDate:Date;
				var prevWeekNumber:int;
				var testWeekNumber:int;
								
				// TODO replace with a real algorithm: I didn't have time to look for one
				for(var day:int = 1; day <= DateUtil.daysInMonths[testMonthDate.month]; ++day)
				{
					testDate = new Date(year, testMonthDate.month, day);
					testWeekNumber = WeekDateUtil.calculateWeekNumber(testDate); 
					
					if ((testWeekNumber == week && isStartDate) || 
						(prevWeekNumber == week && !isStartDate && testWeekNumber != week))
					{
								
						// found first day where this week occurs at
						resultDate = new Date(year, testMonthDate.month, day);
						break; 					
					}
					
					prevWeekNumber = testWeekNumber;
				}
				if (resultDate) break;
				
			}
			
			// if no date was found, we must be at end of month
			if (!resultDate)
			{
				resultDate = new Date(year, testMonthDate.month, day - 1);
			}	
			
			
			return resultDate;
		}
		
		private function gridScrollListener(e:ScrollEvent):void
		{
			if (e.direction == ScrollEventDirection.HORIZONTAL)
			{
				// x axis
				paintRectOffset.x = 0;
				for(var i:int = 0; i < e.position; ++i)
				{
					var col:AdvancedDataGridColumn = (_adg.columns[i] as AdvancedDataGridColumn);
					paintRectOffset.x -= col.width;
				}
				
				adgScrollPosition.x = e.position;
			}
			else if (e.direction == ScrollEventDirection.VERTICAL)
			{
				paintRectOffset.y = -_adg.rowHeight * e.position;
				adgScrollPosition.y = e.position;
			}
			
			updatePaintRect();
		}
		
		private function gridMouseMoveListener(e:MouseEvent):void
		{
			var coords:Point = new Point(e.stageX, e.stageY);
			var adgCoords:Point = _adg.globalToLocal(coords);
		
			coords = _view.overlayCanvas.globalToLocal(coords);
			
			if (adgCoords.y < _adg.headerHeight)
			{
				return;
			}
			
			if (dragMode)
			{
				var snapPos:Point = new Point();
				
				for(var i:int = adgScrollPosition.x; i < _adg.columnCount; ++i)
				{
					var col:AdvancedDataGridColumn = (_adg.columns[i] as AdvancedDataGridColumn);
					if (adgCoords.x > snapPos.x + col.width)
					{
						snapPos.x += col.width;
					}
					else
					{
						break;
					}
				}
				if (paintRectStartColumnIndex != i)
				{
					paintRectEndColumnIndex = i;
					 
					for(i = 0; i < adgScrollPosition.x; ++i)
					{
						col = (_adg.columns[i] as AdvancedDataGridColumn);	
						snapPos.x += col.width;
					}
					
					if (coords.x > paintRect.x)
					{
						paintRectPos.x = rectStartPos.x;
						paintRectSize.x = (snapPos.x - paintRectPos.x);
					}
					else 
					{
						paintRectPos.x = snapPos.x;
						paintRectSize.x = Math.abs(rectStartPos.x - paintRectPos.x);
					}
				}
				
				var rowIndex:int = adgScrollPosition.y + int((coords.y - (_adg.headerHeight * 2)) / _adg.rowHeight);
				var headerHeight:int = (_adg.headerHeight *2);
				snapPos.y = headerHeight + _adg.rowHeight * rowIndex;
				var dpHeight:int = headerHeight + (_adg.dataProvider as IList).length * _adg.rowHeight;
				if (dpHeight < _adg.height && snapPos.y > dpHeight)
				{
					snapPos.y = dpHeight;
				}
				
				if (paintRectStartRowIndex != rowIndex)
				{
					paintRectEndRowIndex = rowIndex;
					
					if (coords.y > paintRect.y)
					{
						
						paintRectPos.y = rectStartPos.y;
						paintRectSize.y = (snapPos.y - paintRectPos.y);
					}
					else 
					{
						paintRectPos.y = snapPos.y;
						paintRectSize.y = Math.abs(rectStartPos.y - paintRectPos.y);
					}
				}
				updatePaintRect();
				
			}
		}
		
		private function dragTimerListener(e:TimerEvent):void
		{
			if (!mouseDown)
			{
				return;		
			}
		
			// disable all sched bar numeric steppers
			if (currentEditItem)
			{
				currentEditItem.editMode = false;
			}
			
			dragMode = true;
			paintRect.mouseEnabled = false;
			paintRect.mouseChildren = false;
			_view.paintRectContents.visible = true;
			paintRect.visible = true;

			// snap x coords to columns
			var snapPos:Point = new Point();
			
			//trace(adgCoords.x, coords.x, adgScrollPosition.x);

			var i:int;
			var col:AdvancedDataGridColumn;
			for(i = adgScrollPosition.x; i < _adg.columnCount; ++i)
			{
				col = (_adg.columns[i] as AdvancedDataGridColumn);

				if (mouseDownCoords.x > snapPos.x + col.width)
				{
					snapPos.x += col.width;	
				}
				else
				{
					break;
				}
			}
			paintRectStartColumnIndex = i;
			for(i = 0; i < adgScrollPosition.x; ++i)
			{
				col = (_adg.columns[i] as AdvancedDataGridColumn);	
				snapPos.x += col.width;
			}
			
			var rowIndex:int = adgScrollPosition.y + int((mouseDownCoords.y - (_adg.headerHeight * 2)) / _adg.rowHeight);
			snapPos.y = (_adg.headerHeight * 2) + _adg.rowHeight * rowIndex;
			
			
			paintRectStartRowIndex = rowIndex;
			//trace("rowIndex is ", rowIndex);
			paintRectEndColumnIndex = paintRectStartColumnIndex + 1;
			paintRectEndRowIndex = rowIndex + 1;
			
			rectStartPos.x = snapPos.x;
			rectStartPos.y = snapPos.y;
			
			paintRectSize.x = (_adg.columns[paintRectStartColumnIndex] as AdvancedDataGridColumn).width;
			paintRectSize.y = _adg.rowHeight;
			
			paintRectPos.x = rectStartPos.x;
			paintRectPos.y = rectStartPos.y;
			
			updatePaintRect();
			
		}
		
		private function gridMouseDownListener(e:MouseEvent):void
		{
			paintRect.visible = false;
			paintRect.mouseEnabled = false;
			
			dragTimer.start();
			
			var coords:Point = new Point(e.stageX, e.stageY);
			mouseDownCoords = _adg.globalToContent(coords);
			
			var maxY:int;
			var dpHeight:int = (_adg.dataProvider as IList).length * _adg.rowHeight;
			if (dpHeight < _adg.height)
			{
				maxY = dpHeight;
			}
			else
			{
				maxY = _adg.rowHeight *  (int((_adg.height - _adg.headerHeight * 2) / _adg.rowHeight) -1) + 
					   _adg.headerHeight * 2;
			}
			
			if (!_editable || 
				mouseDownCoords.y < (_adg.headerHeight * 2) || 
				mouseDownCoords.y > maxY || 
				mouseDownCoords.x > _adg.width - HSCROLLBAR_HEIGHT || 
				_rendererMode != RENDERER_MODE_QUARTER 
				)
			{
				return;
			}
			
			mouseDown = true;
		
		}
		
		private function updatePaintRect():void
		{					
			var finalPos:Point = new Point(paintRectOffset.x + paintRectPos.x, 
										   paintRectOffset.y + paintRectPos.y);
			var finalSize:Point = new Point(paintRectSize.x, paintRectSize.y);
		
			var adgCoords:Point = CoordinateUtils.localToLocal(_view.overlayCanvas, _adg, finalPos);
			
			paintRect.visible = true;
					
			if (adgCoords.y < _adg.headerHeight)
			{
				// resize rect
				var diff:Number = _adg.headerHeight - adgCoords.y;
				finalPos.y = _adg.headerHeight;
				finalSize.y -= diff;
				if (finalSize.y <= 0)
				{
					paintRect.visible = false;
				}
			}
			
			paintRect.x = finalPos.x;
			paintRect.y = finalPos.y;	
			
			paintRect.width = finalSize.x;
			paintRect.height = finalSize.y;
		}
		
		private function gridMouseUpListener(e:MouseEvent):void
		{
			mouseDown = false;
			
			if (dragMode)
			{
				dragMode = false;	
				
				paintRect.mouseEnabled = true;
				paintRect.mouseChildren = true;
				
				_view.paintRectContents.visible = true;
			}
		}
		
		public function set rowHeight(value:Number):void
		{
			_adg.rowHeight = value;
		}
		
		public function resetView():void
		{
			_adg.horizontalScrollPosition = 0;
			rendererMode = RENDERER_MODE_YEAR;
			drawTimeLine();
		}
		
		private function drawTimeLine(year:Number = Number.MIN_VALUE):void
		{
			// default
			var drawStartDate:Date = _startDate;
			if (year != Number.MIN_VALUE)
			{
				_currentYear = year;
				drawStartDate = new Date(year,0, 1);
			}
			
			var grpColsArray:Array = new Array();
			var delta:Number = _endDate.getFullYear() - drawStartDate.getFullYear();
			var tempYear:Number = drawStartDate.getFullYear();
			
			var grpcol:AdvancedDataGridColumnGroup;
			var adgc:AdvancedDataGridColumn;
			var cols:Array;
			
			var num:Number;
			  
			var i:int;
			var j:int;
			var k:int;  
			
			  
			if (_rendererMode == RENDERER_MODE_YEAR)
			{
				for(i = 0; i <= delta; i++)
				{					
					grpcol = new AdvancedDataGridColumnGroup();
				    cols = new Array();
				    if (DateUtil.isLeapYear(tempYear))
				    {
				    	grpcol.width = LEAP_YEAR_COLUMN_WIDTH;
				    }
				    else
				    {
				    	grpcol.width = NORM_YEAR_COLUMN_WIDTH;
				    }
				    grpcol.headerText = String(tempYear++);
					
					
					
					// quarters
					for(k = 0; k < 4 ; ++k)
					{
				    	adgc = new AdvancedDataGridColumn();
				    	adgc.width = grpcol.width / 4;
				    	
				    	adgc.headerText = "Q"+(k+1);
				    	cols.push(adgc);
					}
					    
				    grpcol.children = cols;
				    grpColsArray.push(grpcol);
				    
				}
				
				grpColsArray.push(createBlankColumnGroup());
			} 
			else if(_rendererMode == RENDERER_MODE_QUARTER)
			{
					
				quarterDateBounds = [
								[new Date(drawStartDate.getFullYear(), 0, 1),
								 new Date(drawStartDate.getFullYear(), 2, 31)],
								[new Date(drawStartDate.getFullYear(), 3, 1),
								 new Date(drawStartDate.getFullYear(), 5, 30)],
								[new Date(drawStartDate.getFullYear(), 6, 1),
								 new Date(drawStartDate.getFullYear(), 8, 30)],
								[new Date(drawStartDate.getFullYear(), 9, 1),
								 new Date(drawStartDate.getFullYear(), 11, 31)],
								];
								
				var numOfWeeks:int;
				
			 	for(j = 0; j < 4; ++j)
			  	{
					grpcol= new AdvancedDataGridColumnGroup();
			     	cols = new Array();
			    	grpcol.headerText = "Q" + (j + 1) + " " + String(tempYear);
			    	
			    	if (DateUtil.isLeapYear(drawStartDate.getFullYear()))
				    {
				    	grpcol.width = LEAP_YEAR_QUARTER_COLUMN_WIDTH;
				    }
				    else
				    {
				    	grpcol.width = QUARTER_COLUMN_WIDTH;
				    }
			    	
			    	var startWeek:int = WeekDateUtil.calculateWeekNumber(quarterDateBounds[j][0]);
			    	var endWeek:int = WeekDateUtil.calculateWeekNumber(quarterDateBounds[j][1]);
			    	var previousYearHas53Weeks:Boolean = 
			    		WeekDateUtil.previousYearHas53Weeks(quarterDateBounds[j][0]);
			    	
			    	var startWeekWidth:int;
			    	var endWeekWidth:int;
			    	var startWeekDay:int = WeekDateUtil.getDayByMonday(quarterDateBounds[j][0] as Date);
			    	var endWeekDay:int = WeekDateUtil.getDayByMonday(quarterDateBounds[j][1] as Date);
			    	numOfWeeks = endWeek - startWeek + 1;
			    	
			    	
			    	
			    	startWeekWidth = QUARTER_WEEK_WIDTH * (7 - startWeekDay) / 7;
			    	endWeekWidth = QUARTER_WEEK_WIDTH * (7 - endWeekDay) / 7;
			    	
			    	//trace("start week width: ", startWeekWidth);
			    	//trace((quarterDateBounds[j][0] as Date).toString());
			    	
			    	//	  (quarterDateBounds[j][1] as Date).toString());
			    	//trace(numOfWeeks, startWeek, endWeek, j);
			    	
			    	k = startWeek - 1;
				    do
				    {
				    	if (k > endWeek && 
						   ((previousYearHas53Weeks && k == 53) || 
						   (!previousYearHas53Weeks && k == 52))
						   )
						{	
								k = 1;
						}
						else
						{
							k++;
						}
						
				    	adgc = new AdvancedDataGridColumn();
				    	adgc.headerText = String(k);
				    	adgc.width = QUARTER_WEEK_WIDTH;
				    	adgc.minWidth = QUARTER_WEEK_WIDTH / 7;
				    	
				    	cols.push(adgc);
						
				    } while (k != endWeek);
				    
				    AdvancedDataGridColumn(cols[0]).width = startWeekWidth;
				    AdvancedDataGridColumn(cols[cols.length - 1]).width = endWeekWidth;
				    
				    
				    // can't be made in loop without doing some checks first, this is easier for now
				    /*
				    for(i = 0; i < cols.length; ++i)
				    {
				    	cols[i].width = grpcol.width / cols.length;
				    }
				    */
				    
			    	grpcol.children = cols;
			    	grpColsArray.push(grpcol);
			   	
			   	}
			   	grpColsArray.push(createBlankColumnGroup());
				
			}
			
           	_adg.horizontalScrollPosition = 0;
		   	_adg.groupedColumns = grpColsArray;
		   	_adg.headerRenderer = new ClassFactory(LabelHeaderRenderer);

		   	var rparray:Array = new Array();
		   	var rp:AdvancedDataGridRendererProvider = new AdvancedDataGridRendererProvider();
		   	rp.columnIndex=0;
		   	rp.columnSpan=0;
		   	rp.renderer = new ClassFactory(ScheduleItemRenderer);
		   	rparray.push(rp);	
		   	
		  	_adg.rendererProviders = rparray;
		  	
			refreshTimeline();	
			
		}
		
		/**
		 *  Called when timeline must be refreshed because of new data or because we want to redraw it.
		 */ 	
		private function refreshTimeline():void
		{
			paintRect.visible = false;
			paintRect.mouseEnabled = false;
			_adg.validateNow();	
		}
		
		private function createBlankColumnGroup():AdvancedDataGridColumnGroup
		{
			
		
			var grpcol:AdvancedDataGridColumnGroup = new AdvancedDataGridColumnGroup();
			var adgc:AdvancedDataGridColumn = new AdvancedDataGridColumn();
			adgc.width = 20;
			adgc.headerText = "";
			var cols:Array = [adgc];
			grpcol.children = cols;
			
			return grpcol;
		}
		
		private function gridRowUpdateListener(e:GridItemEditEvent):void
		{
			//trace("updating grid");
			//trace(ObjectUtil.toString((_adg.dataProvider as ArrayCollection)));
			(_adg.dataProvider as ArrayCollection).refresh();
		}
		
		private function gridEditListener(e:GridItemEditEvent):void
		{
			//trace("CAUGHT EVENT ", e);
			if (currentEditItem && currentEditItem != ScheduleBar(e.data))
			{
				currentEditItem.editMode = false;
				currentEditItem = ScheduleBar(e.data);
			}
			else
			{
				currentEditItem = ScheduleBar(e.data);
			}
		}	
		
		private function headerReleaseHandler(event:AdvancedDataGridEvent):void
		{
			
			var currcol:Number = event.columnIndex;
			event.preventDefault();
			
			//trace(event);
			//trace("col index", event.columnIndex);
			//trace("renderer mode", _rendererMode);
			
			
			if (currcol == -1 && _rendererMode == RENDERER_MODE_YEAR)
			{
				var year:int = int(LabelHeaderRenderer(event.itemRenderer).text);
				
				rendererMode = RENDERER_MODE_QUARTER;
				
				drawTimeLine(year);				
			}
			else if (currcol == -1 && _rendererMode == RENDERER_MODE_QUARTER)
			{
				rendererMode = RENDERER_MODE_YEAR;
				drawTimeLine();
			}
			
		
		}
		
		private function getDaysPerYear(year:int):int
		{
			if (DateUtil.isLeapYear(year))
			{
				return 366;
			}
			else
			{
				return 365;	
			}
		}
		
		
		public function get viewportStartDate():Date
		{
			if (_rendererMode == RENDERER_MODE_YEAR)
			{
				return _startDate;
			}
			else if (_rendererMode == RENDERER_MODE_QUARTER)
			{
				return new Date(_currentYear, 0, 1);
			}
			
			return null;
		}
		
		public function get viewportEndDate():Date
		{
			if (_rendererMode == RENDERER_MODE_YEAR)
			{
				return _endDate;
			}
			else if (_rendererMode == RENDERER_MODE_QUARTER)
			{
				return new Date(_currentYear, 11,31);
			}
			
			return null;
		}
		
		public function get maxTimeUnit():Number
		{
			return _maxTimeUnit;
		} 
		
		public function get minTimeUnit():Number
		{
			return _minTimeUnit;
		}
		
	}
}